/**
 * 
 */
package edu.gatech.cs3240.NFA;

import java.util.ArrayList;
import java.util.Hashtable;

import edu.gatech.cs3240.Token;
import edu.gatech.cs3240.NFA.NFA.IllegalNFAOperationException;

/**
 * Represents an individual state in an NFA.
 * @author VRamachandran
 */
public class State { 

	private String name;
	private boolean accepting;
	private ArrayList<Transition> paths;
	
	public State(String name, boolean accept, ArrayList<Transition> paths) {
		this.name = name;
		this.accepting = accept;
		this.paths = paths;
	}
	
	public State(String name, boolean accept) {
		this(name, accept, new ArrayList<Transition>());
	}
	
	public State(boolean accept) {
		this(null, accept);
	}

	public void addTransition(Transition t) throws IllegalNFAOperationException {
		if(t.getSource() != this)
			throw new IllegalNFAOperationException("Attempting to add " +
					"foreign transition to state.");
		else
			paths.add(t);
	}
	
	public void addTransition(State destination, Token consume) {
		Transition t = new Transition(this, destination, consume);
		if(!paths.contains(t))
			paths.add(t);
	}
	
	public void addTransition(State destination) {
		Transition t = new Transition(this, destination);
		if(!paths.contains(t))
			paths.add(t);
	}
	
	/**
	 * Is this an accepting state?
	 * @return
	 */
	public boolean isAccepting() {
		return accepting;
	}
	
	/**
	 * Get the set of possible transitions leading out of this State.
	 * @return
	 */
	public ArrayList<Transition> getPaths() {
		return paths;
	}
	
	public String getName() {
		return name;
	}
	
	public void setAccepting(boolean a) {
		accepting = a;
	}
	
	public static State getEpsilonClosure(State s, ArrayList<State> added) {
		State ns = new State(s.isAccepting());
		Transition t;
		added.add(s);
		for(int i = 0; i < s.getPaths().size(); i++) {
			t = s.getPaths().get(i);
			if(added.contains(t.getTarget())) {
				// I'm looping back to a state I've already consumed.
				if(!t.isEpsilon()) {
					ns.addTransition(ns, t.getConsumable());
				} // If it's an epsilon transition, ignore it.
				continue;
			}
			if(!t.isEpsilon()) {
				ns.addTransition(t.getTarget(), t.getConsumable());
			} else {
				collapse(ns, 
						getEpsilonClosure(s.getPaths().get(i).getTarget(), 
								added));
			}
		}
		return ns;
	}
	

	public void setName(String s) {
		name = s;
	}
	
	/**
	 * Add all the transitions from the second state to the first.
	 * @param first A state.
	 * @param second Another state.
	 */
	private static void collapse(State first, State second) {
		for(int i = 0; i < second.getPaths().size(); i++) {
			first.addTransition(second.getPaths().get(i).getTarget(),
					second.getPaths().get(i).getConsumable());
		}
	}
	
	/**
	 * @see edu.gatech.cs3240.NFA.NFA.toString().
	 */
	public String toString(String prefix, Hashtable<State, Integer> used) {
		if(used.get(this) != null) {
			// This state has already been expanded in the tree, don't bother 
			// expanding again (to avoid an infinite loop).
			if(accepting)
				return "<{" + used.get(this) + "}>\n";
			else
				return "<" + used.get(this) + ">\n";
		} else {
			// The state hasn't been expanded in the output string yet; add it.
			String s;
			if(name == null)
				name = "" + used.size();
			if(accepting) {
				s = "<{" + used.size() + "}>\n";
			} else {
				s = "<" + used.size() + ">\n";
			}
			used.put(this, used.size());
			for(int j = 0; j < paths.size(); j++) {
				if(paths.get(j).isEpsilon())
					s += prefix + "-E-" + "\t";
				else 
					s += prefix + "-" 
						+ paths.get(j).getConsumable().toString() + "-" + "\t";
				s += paths.get(j).getTarget().toString(prefix + "\t", used);
			}
			return s;
		}
	}

	public void removeTransition(Transition transition) {
		paths.remove(transition);
	}

	public void addNonloopingTransitions(State second) {
		for(int i = 0; i < second.getPaths().size(); i++){
			if (second.getPaths().get(i).getTarget() != second)
				this.addTransition(second.getPaths().get(i).getTarget(),
						second.getPaths().get(i).getConsumable());
		}
	}
}